﻿using iTool.Common;
using Orleans;
using Orleans.EventSourcing;
using Orleans.EventSourcing.CustomStorage;
using Orleans.Providers;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace iTool.ClusterComponent
{
    public abstract class iToolServiceBase : Orleans.Grain
    {
        public ProducerQueueHandler<T> GetProducerQueueHandler<T>(string topic, string streamNamespace, string providerName = "iToolSimpleStream")
        {
            IQueueProvider provider = new QueueProvider(this.GetStreamProvider(providerName));
            return new ProducerQueueHandler<T>(provider, topic, streamNamespace);
        }

        protected virtual string GetKeyToString()
        {
            //string stringKey =  this.GrainReference.PrimaryKeyAsString();
            //return stringKey;

            Guid guidKey = this.GetGuidKey(out string keyExt);
            if (guidKey != default(Guid))
            {
                return string.IsNullOrWhiteSpace(keyExt) ? guidKey.ToString() : $"{guidKey}_{keyExt}";
            }

            string stringKey = this.GetStringKey();
            if (!string.IsNullOrWhiteSpace(stringKey))
            {
                return stringKey;
            }

            long longKey = this.GetLongKey(out keyExt);
            return string.IsNullOrWhiteSpace(keyExt) ? longKey.ToString() : $"{longKey}_{keyExt}";

        }
        protected Guid GetGuidKey(out string keyExt) => this.GetPrimaryKey(out keyExt);
        protected long GetLongKey(out string keyExt) => this.GetPrimaryKeyLong(out keyExt);
        protected Guid GetGuidKey() => this.GetPrimaryKey();
        protected long GetLongKey() => this.GetPrimaryKeyLong();
        protected string GetStringKey() => this.GetPrimaryKeyString();

        protected T GetService<T>(Guid primaryKey) where T : iToolServiceWithGuidKey
        {
            return this.GrainFactory.GetGrain<T>(primaryKey);
        }
        protected T GetService<T>(long primaryKey) where T : iToolServiceWithIntegerKey
        {
            if (primaryKey == 0)
            {
                throw new Exception($"primary Key cannot is 0, error");
            }
            return this.GrainFactory.GetGrain<T>(primaryKey);
        }
        protected T GetService<T>(string primaryKey) where T : iToolServiceWithStringKey
        {
            return this.GrainFactory.GetGrain<T>(primaryKey);
        }
        protected T GetService<T>(long primaryKey1, string primaryKey2) where T : iToolServiceWithIntegerCompoundKey
        {
            if (primaryKey1 == 0)
            {
                throw new Exception($"primary Key cannot is 0, error");
            }
            return this.GrainFactory.GetGrain<T>(primaryKey1, primaryKey2);
        }
        protected T GetService<T>(Guid primaryKey1, string primaryKey2) where T : iToolServiceWithGuidCompoundKey
        {
            return this.GrainFactory.GetGrain<T>(primaryKey1, primaryKey2);
        }
        protected T GetServiceByClusterNoder<T>(string targetHost) where T : iToolServiceWithNoder
        {
            return this.GrainFactory.GetGrain<T>(targetHost);
        }
        protected T GetService<T>() where T : iToolService
        {
            return this.GrainFactory.GetGrain<T>(0);
        }

        /// <summary>
        /// 计划释放
        /// </summary>
        /// <param name="delayTimeSpan">延迟释放，-timer 表示取消 dispose, timer = null 在调用完成之后释放 </param>
        protected void PlanDispose(TimeSpan? delayTimeSpan = null)
        {
            if (delayTimeSpan == null)
            {
                this.DeactivateOnIdle();
            }
            else
            {
                this.DelayDeactivation((TimeSpan)delayTimeSpan);
            }
        }
    }


    [StorageProvider(ProviderName = "Default")]
    public abstract class iToolServiceBase<TState> : Grain<TState>
    {
        public ProducerQueueHandler<T> GetProducerQueueHandler<T>(string topic, string streamNamespace, string providerName = "iToolSimpleStream")
        {
            IQueueProvider provider = new QueueProvider(this.GetStreamProvider(providerName));
            return new ProducerQueueHandler<T>(provider, topic, streamNamespace);
        }

        protected virtual string GetKeyToString()
        {
            //string stringKey = this.GrainReference.PrimaryKeyAsString();
            //return stringKey;

            Guid guidKey = this.GetGuidKey(out string keyExt);
            if (guidKey != default(Guid))
            {
                return string.IsNullOrWhiteSpace(keyExt) ? guidKey.ToString() : $"{guidKey}_{keyExt}";
            }

            string stringKey = this.GetStringKey();
            if (!string.IsNullOrWhiteSpace(stringKey))
            {
                return stringKey;
            }

            long longKey = this.GetLongKey(out keyExt);
            return string.IsNullOrWhiteSpace(keyExt) ? longKey.ToString() : $"{longKey}_{keyExt}";

        }
        protected Guid GetGuidKey(out string keyExt) => this.GetPrimaryKey(out keyExt);
        protected long GetLongKey(out string keyExt) => this.GetPrimaryKeyLong(out keyExt);
        protected Guid GetGuidKey() => this.GetPrimaryKey();
        protected long GetLongKey() => this.GetPrimaryKeyLong();
        protected string GetStringKey() => this.GetPrimaryKeyString();

        protected T GetService<T>(Guid primaryKey) where T : iToolServiceWithGuidKey
        {
            return this.GrainFactory.GetGrain<T>(primaryKey);
        }
        protected T GetService<T>(long primaryKey) where T : iToolServiceWithIntegerKey
        {
            if (primaryKey == 0)
            {
                throw new Exception($"primary Key cannot is 0, error");
            }
            return this.GrainFactory.GetGrain<T>(primaryKey);
        }
        protected T GetService<T>(string primaryKey) where T : iToolServiceWithStringKey
        {
            return this.GrainFactory.GetGrain<T>(primaryKey);
        }
        protected T GetService<T>(long primaryKey1, string primaryKey2) where T : iToolServiceWithIntegerCompoundKey
        {
            if (primaryKey1 == 0)
            {
                throw new Exception($"primary Key cannot is 0, error");
            }
            return this.GrainFactory.GetGrain<T>(primaryKey1, primaryKey2);
        }
        protected T GetService<T>(Guid primaryKey1, string primaryKey2) where T : iToolServiceWithGuidCompoundKey
        {
            return this.GrainFactory.GetGrain<T>(primaryKey1, primaryKey2);
        }
        protected T GetServiceByClusterNoder<T>(string targetHost) where T : iToolServiceWithNoder
        {
            return this.GrainFactory.GetGrain<T>(targetHost);
        }
        protected T GetService<T>() where T : iToolService
        {
            return this.GrainFactory.GetGrain<T>(0);
        }

        /// <summary>
        /// 计划释放
        /// </summary>
        /// <param name="delayTimeSpan">延迟释放，-timer 表示取消 dispose, timer = null 在调用完成之后释放 </param>
        protected void PlanDispose(TimeSpan? delayTimeSpan = null)
        {
            if (delayTimeSpan == null)
            {
                this.DeactivateOnIdle();
            }
            else
            {
                this.DelayDeactivation((TimeSpan)delayTimeSpan);
            }
        }

    }


    public abstract class iToolServiceStorageBase<TState> : Grain<TState>
    {
        public ProducerQueueHandler<T> GetProducerQueueHandler<T>(string topic, string streamNamespace, string providerName = "iToolSimpleStream")
        {
            IQueueProvider provider = new QueueProvider(this.GetStreamProvider(providerName));
            return new ProducerQueueHandler<T>(provider, topic, streamNamespace);
        }

        protected virtual string GetKeyToString()
        {
            // string stringKe1y = this.GrainReference.PrimaryKeyAsString();
            // return stringKey;
            long longKey = this.GetLongKey(out string keyExt);
            if (longKey != 0) { 
                return string.IsNullOrWhiteSpace(keyExt) ? longKey.ToString() : $"{longKey}_{keyExt}";
            }

            Guid guidKey = this.GetGuidKey(out  keyExt);
            if (guidKey != default(Guid))
            {
                return string.IsNullOrWhiteSpace(keyExt) ? guidKey.ToString() : $"{guidKey}_{keyExt}";
            }

            string stringKey = this.GetStringKey();
            if (!string.IsNullOrWhiteSpace(stringKey))
            {
                return stringKey;
            }
            
            return this.GrainReference.PrimaryKeyAsString();

        }
        protected Guid GetGuidKey(out string keyExt) => this.GetPrimaryKey(out keyExt);
        protected long GetLongKey(out string keyExt) => this.GetPrimaryKeyLong(out keyExt);
        protected Guid GetGuidKey() => this.GetPrimaryKey();
        protected long GetLongKey() => this.GetPrimaryKeyLong();
        protected string GetStringKey() => this.GetPrimaryKeyString();

        protected T GetService<T>(Guid primaryKey) where T : iToolServiceWithGuidKey
        {
            return this.GrainFactory.GetGrain<T>(primaryKey);
        }
        protected T GetService<T>(long primaryKey) where T : iToolServiceWithIntegerKey
        {
            if (primaryKey == 0)
            {
                throw new Exception($"primary Key cannot is 0, error");
            }
            return this.GrainFactory.GetGrain<T>(primaryKey);
        }
        protected T GetService<T>(string primaryKey) where T : iToolServiceWithStringKey
        {
            return this.GrainFactory.GetGrain<T>(primaryKey);
        }
        protected T GetService<T>(long primaryKey1, string primaryKey2) where T : iToolServiceWithIntegerCompoundKey
        {
            if (primaryKey1 == 0)
            {
                throw new Exception($"primary Key cannot is 0, error");
            }
            return this.GrainFactory.GetGrain<T>(primaryKey1, primaryKey2);
        }
        protected T GetService<T>(Guid primaryKey1, string primaryKey2) where T : iToolServiceWithGuidCompoundKey
        {
            return this.GrainFactory.GetGrain<T>(primaryKey1, primaryKey2);
        }
        protected T GetServiceByClusterNoder<T>(string targetHost) where T : iToolServiceWithNoder
        {
            return this.GrainFactory.GetGrain<T>(targetHost);
        }
        protected T GetService<T>() where T : iToolService
        {
            return this.GrainFactory.GetGrain<T>(0);
        }

        /// <summary>
        /// 计划释放
        /// </summary>
        /// <param name="delayTimeSpan">延迟释放，-timer 表示取消 dispose, timer = null 在调用完成之后释放 </param>
        protected void PlanDispose(TimeSpan? delayTimeSpan = null)
        {
            if (delayTimeSpan == null)
            {
                this.DeactivateOnIdle();
            }
            else
            {
                this.DelayDeactivation((TimeSpan)delayTimeSpan);
            }
        }


        protected new abstract Task ClearStateAsync();
        protected new abstract Task ReadStateAsync();
        protected new abstract Task WriteStateAsync();
        public override async Task OnActivateAsync() 
        {
            await this.ReadStateAsync();
        }
    }


    [LogConsistencyProvider(ProviderName = "CustomStorage")]
    public abstract class iToolServiceBase<TState, TEventBase> :
        JournaledGrain<TState, TEventBase>, ICustomStorageInterface<TState, TEventBase>
        where TState : class, new()
        where TEventBase : class
    {
        IGrainFactory iGrainFactory;
        Timer Timer = null;
        int dueTime;

        /// <param name="dueTime">状态延迟通知 ms</param>
        public iToolServiceBase(int dueTime)
        {
            this.iGrainFactory = iBox.GetService<IGrainFactory>("SiloHostService");
            this.dueTime = dueTime;
            Timer = new Timer(new TimerCallback(state => this.NotifyStateChanged(state as IGrainFactory)), iGrainFactory, Timeout.Infinite, Timeout.Infinite);
        }

        public ProducerQueueHandler<T> GetProducerQueueHandler<T>(string topic, string streamNamespace, string providerName = "iToolSimpleStream")
        {
            IQueueProvider provider = new QueueProvider(this.GetStreamProvider(providerName));
            return new ProducerQueueHandler<T>(provider, topic, streamNamespace);
        }

        protected virtual string GetKeyToString()
        {
            //string stringKey = this.GrainReference.PrimaryKeyAsString();
            //return stringKey;
            Guid guidKey = this.GetGuidKey(out string keyExt);
            if (guidKey != default(Guid))
            {
                return string.IsNullOrWhiteSpace(keyExt) ? guidKey.ToString() : $"{guidKey}_{keyExt}";
            }

            string stringKey = this.GetStringKey();
            if (!string.IsNullOrWhiteSpace(stringKey))
            {
                return stringKey;
            }

            long longKey = this.GetLongKey(out keyExt);
            return string.IsNullOrWhiteSpace(keyExt) ? longKey.ToString() : $"{longKey}_{keyExt}";

        }
        protected Guid GetGuidKey(out string keyExt) => this.GetPrimaryKey(out keyExt);
        protected long GetLongKey(out string keyExt) => this.GetPrimaryKeyLong(out keyExt);
        protected Guid GetGuidKey() => this.GetPrimaryKey();
        protected long GetLongKey() => this.GetPrimaryKeyLong();
        protected string GetStringKey() => this.GetPrimaryKeyString();

        protected T GetService<T>(Guid primaryKey) where T : iToolServiceWithGuidKey
        {
            return this.GrainFactory.GetGrain<T>(primaryKey);
        }
        protected T GetService<T>(long primaryKey) where T : iToolServiceWithIntegerKey
        {
            if (primaryKey == 0)
            {
                throw new Exception($"primary Key cannot is 0, error");
            }
            return this.GrainFactory.GetGrain<T>(primaryKey);
        }
        protected T GetService<T>(string primaryKey) where T : iToolServiceWithStringKey
        {
            return this.GrainFactory.GetGrain<T>(primaryKey);
        }
        protected T GetService<T>(long primaryKey1, string primaryKey2) where T : iToolServiceWithIntegerCompoundKey
        {
            if (primaryKey1 == 0)
            {
                throw new Exception($"primary Key cannot is 0, error");
            }
            return this.GrainFactory.GetGrain<T>(primaryKey1, primaryKey2);
        }
        protected T GetService<T>(Guid primaryKey1, string primaryKey2) where T : iToolServiceWithGuidCompoundKey
        {
            return this.GrainFactory.GetGrain<T>(primaryKey1, primaryKey2);
        }
        protected T GetServiceByClusterNoder<T>(string targetHost) where T : iToolServiceWithNoder
        {
            return this.GrainFactory.GetGrain<T>(targetHost);
        }
        protected T GetService<T>() where T : iToolService
        {
            return this.GrainFactory.GetGrain<T>(0);
        }

        /// <summary>
        /// 计划释放
        /// </summary>
        /// <param name="delayTimeSpan">延迟释放，-timer 表示取消 dispose, timer = null 在调用完成之后释放 </param>
        protected void PlanDispose(TimeSpan? delayTimeSpan = null)
        {
            if (delayTimeSpan == null)
            {
                this.DeactivateOnIdle();
            }
            else
            {
                this.DelayDeactivation((TimeSpan)delayTimeSpan);
            }
        }



        /// <summary>
        /// 校验当前版本号是否落后于实际版本
        /// </summary>
        /// <param name="updates">Events</param>
        /// <param name="expectedversion">当前版本号</param>
        /// <returns></returns>
        public abstract Task<bool> ApplyUpdatesToStorage(IReadOnlyList<TEventBase> updates, int expectedversion);

        /// <summary>
        /// 获取最新的状态和版本号
        /// </summary>
        /// <returns></returns>
        public abstract Task<KeyValuePair<int, TState>> ReadStateFromStorage();

        /// <summary>
        /// 更新状态
        /// </summary>
        /// <param name="state"></param>
        /// <param name="event"></param>
        protected abstract override void TransitionState(TState state, TEventBase @event);

        /// <summary>
        /// 状态变化通知（多个实例情况下）
        /// </summary>
        protected override void OnStateChanged()
        {
            this.Timer.Change(this.dueTime, Timeout.Infinite);
        }
        protected abstract void NotifyStateChanged(IGrainFactory iGrainFactory);
    }

}
